package com.ideaaedi.zoo.example.springboot.bpmn.flowable.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.ideaaedi.zoo.diy.artifact.flowable.convert.BpmnEntityConvert;
import com.ideaaedi.zoo.diy.artifact.flowable.facade.*;
import com.ideaaedi.zoo.example.springboot.bpmn.flowable.controller.demo6.dto.AddSignAtAfterDTO;
import com.ideaaedi.zoo.example.springboot.bpmn.flowable.controller.demo6.dto.CompleteTaskDTO;
import com.ideaaedi.zoo.example.springboot.bpmn.flowable.controller.demo6.enums.BpmnBizSceneEnum;
import com.ideaaedi.zoo.example.springboot.bpmn.flowable.controller.demo6.enums.RunningTaskBizStateEnum;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <pre>
 * 加签：添加其他审批人来共同审批，自己仍需参与审批。例如，不好判断当前审批时，可以添加其他人来共同审批
 * 向前加签：在任务 A 之【前】添加 B、C、D 三个子任务，在 B、C、D 子任务都审批完成后，任务 A 才能继续审批
 * 向后加签：在任务 A 之【后】添加 B、C、D 三个子任务，在任务 A 审批通过（处于审批通过中），只有 B、C、D 三个子任务都审批通过，才会审批通过
 * </pre>
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
@Slf4j
@RestController
@RequestMapping("/demo-向前向后加签")
public class Demo6向前向后加签Controller {

    private static final String TENANT = "1,";


    @Resource
    private HistoryService historyService;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private TaskService taskService;

    @Resource
    private ManagementService managementService;

    private static final String userId4A = "1001";
    private static final String userId4B = "1002";
    private static final String userId4C = "1003";
    private static final String userId4D = "1004";

    /**
     * 方法的最后是否删除测试数据
     */
//    boolean delTestData = true;
    boolean delTestData = false;

    /**
     * 测试审批通过
     * <pre>
     *  测试userId4A、userId4B、userId4C、userId4D都通过
     * </pre>
     */
    @SneakyThrows
    @RequestMapping("/test1")
    @Transactional(rollbackFor = Exception.class)
    public void test1() {
        List<String> printInfo = new ArrayList<>();
        // step1. 部署资源，并获取 流程定义key
        Deployment deployment = repositoryService.createDeployment().tenantId(TENANT)
                .addBytes("加签.bpmn20.xml", ResourceUtil.readBytes("加签.bpmn20.xml"))
                .name("加签")
                .deploy();
        BpmnDeployment bpmnDeployment = BpmnEntityConvert.convert2BpmnDeployment(deployment);

        List<? extends BpmnProcessDefinition> processDefinitions = Optional.ofNullable(
                repositoryService.createProcessDefinitionQuery()
                        .processDefinitionTenantIdLike(TENANT + "%")
                        .deploymentId(bpmnDeployment.getId())
                        .list()
            ).map(x -> x.stream()
                    .map(BpmnEntityConvert::convert2BpmnProcessDefinition)
                    .toList()
            ).orElse(Collections.emptyList());

        String processDefinitionKey = processDefinitions.get(0).getKey();

        // step2. 发起流程实例（此时请假1天，那么审核任何应该到组长那里）
        String businessKey = "业务标识_" + IdUtil.fastSimpleUUID();
        ProcessInstance processInstance;
        try {
            String userId = null;
            // Flowable发起流程时，会直接从登录态中获取当前用户作为流程发起人
            Authentication.setAuthenticatedUserId(userId);
            processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, businessKey,
                    null, TENANT);
        } finally {
            Authentication.setAuthenticatedUserId(null);
        }
        BpmnProcessInstance bpmnProcessInstance = BpmnEntityConvert.convert2BpmnProcessInstance(processInstance);

        String processInstanceId = bpmnProcessInstance.getId();

        // step3. 查看此时的任务流转节点
        /// List<? extends BpmnTask> list =
        ///         bpmnTaskService.list(TENANT,
        ///         BpmnTaskQuery.builder().processInstanceId(processInstanceId).build()
        /// );
        List<? extends BpmnTask> list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();

        BpmnTask bpmnTask = list.get(0);
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" +
                JSONUtil.parse(bpmnTask,
                        JSONConfig.create()
                                .setIgnoreNullValue(true)).toStringPretty()
        );

        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4A)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4A)
                        .files("fileId_" + userId4A)
                        .procVars(Map.of("procVarDemo_" + userId4A, "procVarDemoValue_" + userId4A))
                        .subAssigneeList(Lists.newArrayList(userId4B, userId4C, userId4D))
                        .build(),
                true
        );


        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();


        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());

        bpmnTask = list.get(0);
        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4B)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4B)
                        .files("fileId_" + userId4B)
                        .procVars(Map.of("procVarDemo_" + userId4B, "procVarDemoValue_" + userId4B))
                        .build(),
                true
        );


        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());


        bpmnTask = list.get(0);
        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4C)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4C)
                        .files("fileId_" + userId4C)
                        .procVars(Map.of("procVarDemo_" + userId4C, "procVarDemoValue_" + userId4C))
                        .build(),
                true
        );


        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());


        if (!CollectionUtils.isEmpty(list)) {
            bpmnTask = list.get(0);
            complete(
                    CompleteTaskDTO.builder()
                            .tenant(TENANT)
                            .completedBy(userId4D)
                            .taskId(bpmnTask.getId())
                            .approvalOpinions("审批意见_" + userId4D)
                            .files("fileId_" + userId4D)
                            .procVars(Map.of("procVarDemo_" + userId4D, "procVarDemoValue_" + userId4D))
                            .build(),
                    true
            );

            list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                    .processInstanceId(processInstanceId)
                    .includeProcessVariables()
                    .includeTaskLocalVariables()
                    .or()
                    .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                    .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                    .endOr()
                    .list().stream().map(this::convertBmpTask).toList();
            // 输出： 当前的任务是：组长
            printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());
        }

        List<? extends BpmnHisTask> bpmnHisTasks = Optional.ofNullable(historyService.createHistoricTaskInstanceQuery()
                        .taskTenantIdLike(TENANT + "%")
                        .processInstanceId(processInstanceId)
                        .includeTaskLocalVariables()
                        .includeProcessVariables()
                        .list()
                )
                .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnHisTask).toList())
                .orElse(Collections.emptyList());

        for (BpmnHisTask bpmnHisTask : bpmnHisTasks) {
            List<? extends BpmnComment> taskComments = Optional.ofNullable(taskService.getTaskComments(bpmnHisTask.getId()))
                    .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnComment).toList())
                    .orElse(Collections.emptyList());

            List<? extends BpmnAttachment> taskAttachments = Optional.ofNullable(taskService.getTaskAttachments(bpmnHisTask.getId()))
                    .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnAttachment).toList())
                    .orElse(Collections.emptyList());
            printInfo.add("【流程已结束】任务（" + bpmnHisTask.getId()+ "）评论：" + taskComments.stream().map(BpmnComment::getFullMessage).toList());
            printInfo.add("【流程已结束】任务（" + bpmnHisTask.getId()+ "）附件ids：" + taskAttachments.stream().map(BpmnAttachment::getContentUrl).toList());
            // 把rwa字段清空，免得控制台打印太多东西影响观察
            ((DefaultBpmnHisTask)bpmnHisTask).setRaw(null);
        }
        printInfo.add("【流程已结束】任务执行信息：" + JSONUtil.parse(bpmnHisTasks, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());

        // step5. 删除相关数据
        if (delTestData) {
            repositoryService.deleteDeployment(bpmnDeployment.getId(), true);
        }

        // 打印信息并观察
        boolean flag = true;
        for (String info : printInfo) {
            if (flag) {
                System.err.println(info);
            } else {
                System.out.println(info);
            }
            flag = !flag;
            TimeUnit.MILLISECONDS.sleep(200);
            System.err.println();
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }


    /**
     * 测试审批不通过
     * <pre>
     *     测试userId4A、userId4B通过，userId4C不通过
     * </pre>
     */
    @SneakyThrows
    @RequestMapping("/test2")
    @Transactional(rollbackFor = Exception.class)
    public void test2() {
        List<String> printInfo = new ArrayList<>();

        // step1. 部署资源，并获取 流程定义key
        Deployment deployment = repositoryService.createDeployment().tenantId(TENANT)
                .addBytes("加签.bpmn20.xml", ResourceUtil.readBytes("加签.bpmn20.xml"))
                .name("加签")
                .deploy();
        BpmnDeployment bpmnDeployment = BpmnEntityConvert.convert2BpmnDeployment(deployment);
        List<? extends BpmnProcessDefinition> processDefinitions = Optional.ofNullable(
                repositoryService.createProcessDefinitionQuery()
                        .processDefinitionTenantIdLike(TENANT + "%")
                        .deploymentId(bpmnDeployment.getId())
                        .list()
        ).map(x -> x.stream()
                .map(BpmnEntityConvert::convert2BpmnProcessDefinition)
                .toList()
        ).orElse(Collections.emptyList());
        String processDefinitionKey = processDefinitions.get(0).getKey();

        // step2. 发起流程实例（此时请假1天，那么审核任何应该到组长那里）
        String businessKey = "业务标识_" + IdUtil.fastSimpleUUID();
        ProcessInstance processInstance;
        try {
            String userId = null;
            // Flowable发起流程时，会直接从登录态中获取当前用户作为流程发起人
            Authentication.setAuthenticatedUserId(userId);
            processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, businessKey,
                    null, TENANT);
        } finally {
            Authentication.setAuthenticatedUserId(null);
        }
        BpmnProcessInstance bpmnProcessInstance = BpmnEntityConvert.convert2BpmnProcessInstance(processInstance);
        String processInstanceId = bpmnProcessInstance.getId();

        // step3. 查看此时的任务流转节点
        /// List<? extends BpmnTask> list =
        ///         bpmnTaskService.list(TENANT,
        ///         BpmnTaskQuery.builder().processInstanceId(processInstanceId).build()
        /// );
        List<? extends BpmnTask> list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();

        BpmnTask bpmnTask = list.get(0);
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" +
                JSONUtil.parse(bpmnTask,
                        JSONConfig.create()
                                .setIgnoreNullValue(true)).toStringPretty()
        );

        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4A)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4A)
                        .files("fileId_" + userId4A)
                        .procVars(Map.of("procVarDemo_" + userId4A, "procVarDemoValue_" + userId4A))
                        .subAssigneeList(Lists.newArrayList(userId4B, userId4C, userId4D))
                        .build(),
                true
        );


        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();


        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());

        bpmnTask = list.get(0);
        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4B)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4B)
                        .files("fileId_" + userId4B)
                        .procVars(Map.of("procVarDemo_" + userId4B, "procVarDemoValue_" + userId4B))
                        .build(),
                true
        );


        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());


        bpmnTask = list.get(0);
        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4C)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4C)
                        .files("fileId_" + userId4C)
                        .procVars(Map.of("procVarDemo_" + userId4C, "procVarDemoValue_" + userId4C))
                        .build(),
                false
        );


        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());


        if (!CollectionUtils.isEmpty(list)) {
            bpmnTask = list.get(0);
            complete(
                    CompleteTaskDTO.builder()
                            .tenant(TENANT)
                            .completedBy(userId4D)
                            .taskId(bpmnTask.getId())
                            .approvalOpinions("审批意见_" + userId4D)
                            .files("fileId_" + userId4D)
                            .procVars(Map.of("procVarDemo_" + userId4D, "procVarDemoValue_" + userId4D))
                            .build(),
                    true
            );

            list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                    .processInstanceId(processInstanceId)
                    .includeProcessVariables()
                    .includeTaskLocalVariables()
                    .or()
                    .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                    .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                    .endOr()
                    .list().stream().map(this::convertBmpTask).toList();
            // 输出： 当前的任务是：组长
            printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());
        }


        List<? extends BpmnHisTask> bpmnHisTasks = Optional.ofNullable(historyService.createHistoricTaskInstanceQuery()
                        .taskTenantIdLike(TENANT + "%")
                        .processInstanceId(processInstanceId)
                        .includeTaskLocalVariables()
                        .includeProcessVariables()
                        .list()
                )
                .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnHisTask).toList())
                .orElse(Collections.emptyList());

        for (BpmnHisTask bpmnHisTask : bpmnHisTasks) {
            List<? extends BpmnComment> taskComments = Optional.ofNullable(taskService.getTaskComments(bpmnHisTask.getId()))
                    .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnComment).toList())
                    .orElse(Collections.emptyList());
            List<? extends BpmnAttachment> taskAttachments = Optional.ofNullable(taskService.getTaskAttachments(bpmnHisTask.getId()))
                    .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnAttachment).toList())
                    .orElse(Collections.emptyList());
            printInfo.add("【流程已结束】任务（" + bpmnHisTask.getId()+ "）评论：" + taskComments.stream().map(BpmnComment::getFullMessage).toList());
            printInfo.add(("【流程已结束】任务（" + bpmnHisTask.getId()+ "）附件ids：" + taskAttachments.stream().map(BpmnAttachment::getContentUrl).toList()));
            // 把rwa字段清空，免得控制台打印太多东西影响观察
            ((DefaultBpmnHisTask)bpmnHisTask).setRaw(null);
        }
        printInfo.add("【流程已结束】任务执行信息：" + JSONUtil.parse(bpmnHisTasks, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());

        // step5. 删除相关数据
        if (delTestData) {
            repositoryService.deleteDeployment(bpmnDeployment.getId(), true);
        }

        // 打印信息并观察
        boolean flag = true;
        for (String info : printInfo) {
            if (flag) {
                System.err.println(info);
            } else {
                System.out.println(info);
            }
            flag = !flag;
            TimeUnit.MILLISECONDS.sleep(200);
            System.err.println();
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }

    /**
     * 测试终止流程
     * <pre>
     * 测试userId4A、userId4B通过后，直接终止流程看看
     * </pre>
     */
    @SneakyThrows
    @RequestMapping("/test3")
    @Transactional(rollbackFor = Exception.class)
    public void test3() {
        List<String> printInfo = new ArrayList<>();

        // step1. 部署资源，并获取 流程定义key
        Deployment deployment = repositoryService.createDeployment().tenantId(TENANT)
                .addBytes("加签.bpmn20.xml", ResourceUtil.readBytes("加签.bpmn20.xml"))
                .name("加签")
                .deploy();
        BpmnDeployment bpmnDeployment = BpmnEntityConvert.convert2BpmnDeployment(deployment);
        List<? extends BpmnProcessDefinition> processDefinitions = Optional.ofNullable(
                repositoryService.createProcessDefinitionQuery()
                        .processDefinitionTenantIdLike(TENANT + "%")
                        .deploymentId(bpmnDeployment.getId())
                        .list()
        ).map(x -> x.stream()
                .map(BpmnEntityConvert::convert2BpmnProcessDefinition)
                .toList()
        ).orElse(Collections.emptyList());
        String processDefinitionKey = processDefinitions.get(0).getKey();

        // step2. 发起流程实例（此时请假1天，那么审核任何应该到组长那里）
        String businessKey = "业务标识_" + IdUtil.fastSimpleUUID();
        ProcessInstance processInstance;
        try {
            String userId = null;
            // Flowable发起流程时，会直接从登录态中获取当前用户作为流程发起人
            Authentication.setAuthenticatedUserId(userId);
            processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, businessKey,
                    null, TENANT);
        } finally {
            Authentication.setAuthenticatedUserId(null);
        }
        BpmnProcessInstance bpmnProcessInstance = BpmnEntityConvert.convert2BpmnProcessInstance(processInstance);
        String processInstanceId = bpmnProcessInstance.getId();

        // step3. 查看此时的任务流转节点
        /// List<? extends BpmnTask> list =
        ///         bpmnTaskService.list(TENANT,
        ///         BpmnTaskQuery.builder().processInstanceId(processInstanceId).build()
        /// );
        List<? extends BpmnTask> list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();

        BpmnTask bpmnTask = list.get(0);
        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" +
                JSONUtil.parse(bpmnTask,
                        JSONConfig.create()
                                .setIgnoreNullValue(true)).toStringPretty()
        );

        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4A)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4A)
                        .files("fileId_" + userId4A)
                        .procVars(Map.of("procVarDemo_" + userId4A, "procVarDemoValue_" + userId4A))
                        .subAssigneeList(Lists.newArrayList(userId4B, userId4C, userId4D))
                        .build(),
                true
        );

        list = taskService.createTaskQuery().taskTenantIdLike(TENANT + "%")
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .or()
                .taskVariableNotExists(RUNNING_TASK_BIZ_STATE)
                .taskVariableValueEquals(RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name())
                .endOr()
                .list().stream().map(this::convertBmpTask).toList();


        // 输出： 当前的任务是：组长
        printInfo.add("【流程进行中】当前的任务是：" + JSONUtil.parse(list, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());

        bpmnTask = list.get(0);
        complete(
                CompleteTaskDTO.builder()
                        .tenant(TENANT)
                        .completedBy(userId4B)
                        .taskId(bpmnTask.getId())
                        .approvalOpinions("审批意见_" + userId4B)
                        .files("fileId_" + userId4B)
                        .procVars(Map.of("procVarDemo_" + userId4B, "procVarDemoValue_" + userId4B))
                        .build(),
                true
        );

        /*
         * 还有子任务 userId4C  userId4D 还没有审核，此时直接结束流程看看
         */
        terminateProcess(TENANT, processInstanceId);

        List<? extends BpmnHisTask> bpmnHisTasks = Optional.ofNullable(historyService.createHistoricTaskInstanceQuery()
                        .taskTenantIdLike(TENANT + "%")
                        .processInstanceId(processInstanceId)
                        .includeTaskLocalVariables()
                        .includeProcessVariables()
                        .list()
                )
                .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnHisTask).toList())
                .orElse(Collections.emptyList());
        for (BpmnHisTask bpmnHisTask : bpmnHisTasks) {
            List<? extends BpmnComment> taskComments = Optional.ofNullable(taskService.getTaskComments(bpmnHisTask.getId()))
                    .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnComment).toList())
                    .orElse(Collections.emptyList());
            List<? extends BpmnAttachment> taskAttachments = Optional.ofNullable(taskService.getTaskAttachments(bpmnHisTask.getId()))
                    .map(x -> x.stream().map(BpmnEntityConvert::convert2BpmnAttachment).toList())
                    .orElse(Collections.emptyList());
            printInfo.add("【流程已结束】任务（" + bpmnHisTask.getId()+ "）评论：" + taskComments.stream().map(BpmnComment::getFullMessage).toList());
            printInfo.add("【流程已结束】任务（" + bpmnHisTask.getId()+ "）附件ids：" + taskAttachments.stream().map(BpmnAttachment::getContentUrl).toList());
            // 把rwa字段清空，免得控制台打印太多东西影响观察
            ((DefaultBpmnHisTask)bpmnHisTask).setRaw(null);
        }
        printInfo.add("【流程已结束】任务执行信息：" + JSONUtil.parse(bpmnHisTasks, JSONConfig.create().setIgnoreNullValue(true)).toStringPretty());

        // step5. 删除相关数据
        if (delTestData) {
            repositoryService.deleteDeployment(bpmnDeployment.getId(), true);
        }

        // 打印信息并观察
        boolean flag = true;
        for (String info : printInfo) {
            if (flag) {
                System.err.println(info);
            } else {
                System.out.println(info);
            }
            flag = !flag;
            TimeUnit.MILLISECONDS.sleep(200);
            System.err.println();
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }

    /**
     * 运行时任务业务状态
     */
    public static final String RUNNING_TASK_BIZ_STATE = "__RunningTaskBizState__";

    /**
     * 任务本地变量记录任务的完成人， key
     */
    String LOCAL_TASK_COMPLETED_BY = "__LOCAL_TASK_COMPLETED_BY__";

    /**
     * 任务本地变量记录任务的完成时间， key
     */
    String LOCAL_TASK_COMPLETED_AT = "__LOCAL_TASK_COMPLETED_AT__";

    /**
     * 完成任务
     *
     * @param param 参数
     * @param pass 是否审批通过（true-通过；false-不通过）
     */
    public void complete(CompleteTaskDTO param, boolean pass) {
        String tenant = param.getTenant();
        String taskId = param.getTaskId();
        String completedBy = param.getCompletedBy();
        List<String> subAssigneeList = param.getSubAssigneeList();
        Map<String, Object> procVars = param.getProcVars();
        Map<String, Object> localVars = param.getLocalVars();
        String approvalOpinions = param.getApprovalOpinions();
        String files = param.getFiles();

        TaskEntity currTask = obtainTaskById(tenant, taskId);

        // 如果没通过
        if (!pass) {
            recordCompleteInfo(currTask, completedBy, procVars, localVars, approvalOpinions, files, false);
            taskService.complete(currTask.getId(), completedBy, null);

            // 完成当前任务的父任务（需要的话）
            String parentTaskId = currTask.getParentTaskId();
            while (parentTaskId != null) {
                int count = listSubTaskIds(tenant, parentTaskId).size();
                // 完成父任务
                if (count <= 0) {
                    taskService.resolveTask(parentTaskId);
                    TaskEntity parentTask = obtainTaskById(tenant, parentTaskId);
                    // 清空 scopeType 字段，方便后续可以继续加签
                    TaskEntityImpl parentTaskImpl = (TaskEntityImpl) parentTask;
                    parentTaskImpl.setScopeType(null);
                    taskService.saveTask(parentTaskImpl);

                    // 完成父任务
                    Object variableLocalByObj = taskService.getVariableLocal(parentTaskId, LOCAL_TASK_COMPLETED_BY);
                    String parentTaskCompletedBy = variableLocalByObj == null ? parentTask.getAssignee() : variableLocalByObj.toString();
                    taskService.complete(parentTaskId, parentTaskCompletedBy, null);
                    parentTaskId = parentTask.getParentTaskId();
                } else {
                    break;
                }
            }

            // 终止流程
            terminateProcess(tenant, currTask.getProcessInstanceId());
            return;
        }

        // 是否需要向后加签
        boolean needAddSignAfter = !CollectionUtils.isEmpty(subAssigneeList);
        if (needAddSignAfter) {
            // 完成当前任务
            currTask.setOwner(completedBy);
            currTask.setAssignee(null);
            taskService.saveTask(currTask);
            recordCompleteInfo(currTask, completedBy, procVars, localVars, approvalOpinions, files, true);

            // 向后加签
            AddSignAtAfterDTO addSignAtAfter = new AddSignAtAfterDTO();
            addSignAtAfter.setTaskId(taskId);
            addSignAtAfter.setSubAssigneeList(subAssigneeList);
            addAfterSignTask(addSignAtAfter, tenant, true);
        } else {
            // 当前任务是否还有向后加签的任务
            if (BpmnBizSceneEnum.XHJQ.ifCurrSceneByScope(currTask.getScopeType())) {
                // 完成当前任务
                currTask.setOwner(completedBy);
                currTask.setAssignee(null);
                taskService.saveTask(currTask);
                recordCompleteInfo(currTask, completedBy, procVars, localVars, approvalOpinions, files, true);

                // 唤醒子任务
                List<String> subTaskIds = listSubTaskIds(tenant, taskId);
                for (String subTaskId : subTaskIds) {
                    taskService.setVariableLocal(subTaskId, RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.RUNNING.name());
                    taskService.resolveTask(subTaskId);
                }
            } else {
                // 完成当前任务
                recordCompleteInfo(currTask, completedBy, procVars, localVars, approvalOpinions, files, true);
                taskService.complete(taskId, completedBy, null);
            }

            // 完成当前任务的父任务（需要的话）
            String parentTaskId = currTask.getParentTaskId();
            while (parentTaskId != null) {
                int count = listSubTaskIds(tenant, parentTaskId).size();
                // 完成父任务
                if (count <= 0) {
                    taskService.resolveTask(parentTaskId);
                    TaskEntity parentTask = obtainTaskById(tenant, parentTaskId);
                    // 清空 scopeType 字段，方便后续可以继续加签
                    TaskEntityImpl parentTaskImpl = (TaskEntityImpl) parentTask;
                    parentTaskImpl.setScopeType(null);
                    taskService.saveTask(parentTaskImpl);

                    // 完成父任务
                    Object variableLocalByObj = taskService.getVariableLocal(parentTaskId, LOCAL_TASK_COMPLETED_BY);
                    String parentTaskCompletedBy = variableLocalByObj == null ? parentTask.getAssignee() : variableLocalByObj.toString();
                    taskService.complete(parentTaskId, parentTaskCompletedBy, null);
                    parentTaskId = parentTask.getParentTaskId();
                } else {
                    break;
                }
            }
        }
    }


    /**
     * 终止流程
     *
     * @param processInstanceId 流程实例id
     */
    public void terminateProcess(String tenant, String processInstanceId) {
        // step1. 查询运行中的任务
        List<? extends BpmnTask> taskList = Optional.ofNullable(
                taskService.createTaskQuery()
                        .taskTenantIdLike(tenant + "%")
                        .processInstanceId(processInstanceId)
                        .includeTaskLocalVariables()
                        .list()
        ).map(x -> x.stream()
                .map(BpmnEntityConvert::convert2BpmnTask)
                .toList()
        ).orElse(Collections.emptyList());

        if (CollUtil.isEmpty(taskList)) {
            return;
        }

        // step2. 对于那些业务上尚未结束的任务，直接取消
        List<RunningTaskBizStateEnum> endedRunningTaskStateSet = Arrays.stream(RunningTaskBizStateEnum.values())
                .filter(x -> x != RunningTaskBizStateEnum.RUNNING)
                .collect(Collectors.toList());
        taskList.stream()
                // 过滤掉已经是业务结束状态的运行时任务
                .filter(task -> {
                    Map<String, Object> taskLocalVariables = task.getTaskLocalVariables();
                    Object taskStateObj = taskLocalVariables == null ? null : taskLocalVariables.get(RUNNING_TASK_BIZ_STATE);
                    return taskStateObj == null || !RunningTaskBizStateEnum.contain(endedRunningTaskStateSet, taskStateObj.toString());
                }).forEach(task -> {
                    // 将任务的业务状态设置为取消
                    taskService.setVariableLocal(task.getId(), RUNNING_TASK_BIZ_STATE, RunningTaskBizStateEnum.CANCEL.name());
                });

        // step3. 终止流程（直接把当前活动节点跳转到代表结束事件的活动节点）
        String processDefinitionId = taskList.get(0).getProcessDefinitionId();
        Set<String> taskDefinitionKey = taskList.stream().map(BpmnTask::getTaskDefinitionKey).collect(Collectors.toSet());
        EndEvent endEvent = obtainBpmnModelMainEndEvent(processDefinitionId);
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(processInstanceId)
                .moveActivityIdsToSingleActivityId(Lists.newArrayList(taskDefinitionKey), endEvent.getId())
                .changeState();
    }

    /**
     * 记录完成任务时的相关信息
     *
     * @param currTask 当前任务
     * @param completedBy 完成人
     * @param procVars 流程变量
     * @param localVars 本地变量
     * @param approvalOpinions 审批意见
     * @param fileIds 相关附件
     * @param pass 当前任务审批是否通过（true-通过；false-不通过）
     */
    protected void recordCompleteInfo(@NonNull Task currTask,
                                      @NonNull String completedBy,
                                      @Nullable Map<String, Object> procVars,
                                      @Nullable Map<String, Object> localVars,
                                      @Nullable String approvalOpinions,
                                      @Nullable String fileIds, boolean pass) {
        String taskId = currTask.getId();
        String processInstanceId = currTask.getProcessInstanceId();
        if (localVars == null) {
            localVars = new HashMap<>(8);
        }
        localVars.putAll(
                Map.of(
                        RUNNING_TASK_BIZ_STATE, pass ? RunningTaskBizStateEnum.PASS.name() : RunningTaskBizStateEnum.REJECT.name(),
                        LOCAL_TASK_COMPLETED_BY, completedBy,
                        LOCAL_TASK_COMPLETED_AT, System.currentTimeMillis()
                )
        );
        checkVarValueType(localVars);
        checkVarValueType(procVars);
        taskService.setVariablesLocal(taskId, localVars);
        runtimeService.setVariables(processInstanceId, procVars);
        if (StringUtils.isNotBlank(approvalOpinions)) {
            taskService.addComment(taskId, processInstanceId, approvalOpinions);
        }
        if (StringUtils.isNotBlank(fileIds)) {
            taskService.createAttachment("default", taskId, processInstanceId,
                    "default", "default", fileIds);
        }
    }

    /**
     * 获取任务
     *
     * @param tenant 租户
     * @param taskId 任务id
     * @return 任务
     */
    protected TaskEntity obtainTaskById(String tenant, String taskId) {
        TaskQuery taskQuery = taskService.createTaskQuery();
        TaskEntity currTask = (TaskEntity) taskQuery
                .taskId(taskId)
                .taskTenantIdLike(tenant + "%")
                .includeTaskLocalVariables()
                .singleResult();
        Objects.requireNonNull(currTask, "non runtime task. taskId -> " + taskId);
        return currTask;
    }

    /**
     * 获取剩余子任务id
     *
     * @param tenant       租户
     * @param parentTaskId 父任务 ID
     * @return 剩余子任务id
     */
    protected List<String> listSubTaskIds(String tenant, String parentTaskId) {
        String tableName = managementService.getTableName(TaskEntity.class);
        String sql = "SELECT ID_ from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId} AND TENANT_ID_ like #{tenant}";
        return taskService.createNativeTaskQuery().sql(sql)
                .parameter("parentTaskId", parentTaskId)
                .parameter("tenant", tenant + "%")
                .list()
                .stream()
                .map(Task::getId)
                .collect(Collectors.toList());
    }

    /**
     * 向后加签
     *
     * @param addAfterSign   向后加签参数
     * @param tenant         租户
     * @param runningSubTask 是否将子任务业务状态标记为进行中
     *                       <pre>
     *                                             true-子任务会被标记为RUNNING，子任务的子任务等会被标记为NOT_START
     *                                             false-子任务、子任务的子任务等会被标记为NOT_START
     *                                             </pre>
     */
    protected void addAfterSignTask(AddSignAtAfterDTO addAfterSign, String tenant, boolean runningSubTask) {
        List<String> subAssigneeList = addAfterSign.getSubAssigneeList();
        String taskId = addAfterSign.getTaskId();
        Objects.requireNonNull(taskId, "taskId cannot be null.");
        Assert.isTrue(!CollectionUtils.isEmpty(subAssigneeList), "subAssigneeList cannot be null.");
        TaskQuery taskQuery = taskService.createTaskQuery();
        TaskEntity rootParentTask = (TaskEntity) taskQuery
                .taskId(taskId)
                .taskTenantIdLike(tenant + "%")
                .singleResult();
        Objects.requireNonNull(rootParentTask, "non runtime task. taskId -> " + taskId);
        // scopeType 不为空，说明该任务已经被加签过了，不支持再加签
        String scopeType = rootParentTask.getScopeType();
        if (scopeType != null) {
            throw new RuntimeException("this task does not support post-signing.");
        }
        TaskEntity parentTask = rootParentTask;
        for (int i = 0; i < subAssigneeList.size(); i++) {
            // 开启计数
            System.err.println("taskId -> " + parentTask.getId());
            parentTask.setScopeType(BpmnBizSceneEnum.XHJQ.getScopeType());
            ((TaskEntityImpl) parentTask).setCountEnabled(true);
            taskService.saveTask(parentTask);

            parentTask = createSubTask(parentTask, parentTask.getId(), subAssigneeList.get(i),
                    (runningSubTask && i == 0) ? RunningTaskBizStateEnum.RUNNING : RunningTaskBizStateEnum.NOT_START);
        }
    }

    /**
     * 创建子任务（即：根据源任务，创建指定父任务id的子任务）
     *
     * @param sourceTask      源任务
     *                        <p>
     *                        子任务的部分属性将从源任务中拷贝
     *                        </p>
     * @param parentTaskId    父任务id
     * @param subTaskAssignee 子任务办理人
     * @param state           子任务业务状态
     * @return 子任务
     */
    protected TaskEntity createSubTask(TaskEntity sourceTask, String parentTaskId,
                                       String subTaskAssignee, RunningTaskBizStateEnum state) {
        TaskEntity subtask = (TaskEntity) taskService.newTask(BpmnBizSceneEnum.XHJQ.generateId());
        subtask.setOwner(subTaskAssignee);
        switch (state) {
            case RUNNING -> {
                subtask.setAssignee(subTaskAssignee);
            }
            case NOT_START -> {
                subtask.setAssignee(null);
            }
            default -> throw new RuntimeException("unsupported state. state -> " + state);
        }
        subtask.setAssignee(subTaskAssignee);
        subtask.setCreateTime(new Date());
        subtask.setParentTaskId(parentTaskId);
        subtask.setName(sourceTask.getName());
        // 以下字段和源任务字段保持一致
        subtask.setDescription(sourceTask.getDescription());
        subtask.setCategory(sourceTask.getCategory());
        subtask.setProcessDefinitionId(sourceTask.getProcessDefinitionId());
        subtask.setProcessInstanceId(sourceTask.getProcessInstanceId());
        subtask.setTaskDefinitionKey(sourceTask.getTaskDefinitionKey());
        subtask.setTaskDefinitionId(sourceTask.getTaskDefinitionId());
        subtask.setPriority(sourceTask.getPriority());
        subtask.setTenantId(sourceTask.getTenantId());
        taskService.saveTask(subtask);
        taskService.setVariableLocal(subtask.getId(), RUNNING_TASK_BIZ_STATE, state.name());
        Task task = taskService.createTaskQuery().taskId(subtask.getId()).singleResult();
        return (TaskEntity) task;
    }

    /**
     * 获取bpmn主流程上的结束事件
     *
     * @param processDefinitionId 流程定义id
     *
     * @return 主流程上的结束事件
     */
    @NonNull
    private EndEvent obtainBpmnModelMainEndEvent(String processDefinitionId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        // 此方法只关注用户任务，其余方法忽略
        Process mainProcess = bpmnModel.getMainProcess();
        EndEvent endEvent = null;
        for (FlowElement flowElement : mainProcess.getFlowElements()) {
            if (flowElement instanceof EndEvent currEndEvent) {
                endEvent = currEndEvent;
                break;
            }
        }
        Assert.notNull(endEvent, "结束节点不能未空");
        return endEvent;
    }

    /**
     * 工作流变量值必须为String或数值
     *
     * @param variables 流程变量或局部变量
     */
    protected void checkVarValueType(@Nullable Map<String, Object> variables) {
        if (CollectionUtils.isEmpty(variables)) {
            return;
        }
        for (Object value : variables.values()) {
            if (value == null) {
                continue;
            }
            if (value instanceof String) {
                continue;
            }
            if (value instanceof Number) {
                continue;
            }
            throw new RuntimeException("curr entry value type -> " + value.getClass().getSimpleName() +  ". 工作流变量值必须为String或数值");
        }
    }

    private DefaultBpmnTask convertBmpTask(Task task) {
        if (task == null) {
            return null;
        }
        return DefaultBpmnTask.builder()
                .id(task.getId())
                .name(task.getName())
                .description(task.getDescription())
                .pid(task.getParentTaskId())
                .category(task.getCategory())
                .tenant(task.getTenantId())
                .state(task.getState())
                .dueDate(task.getDueDate())
                .createTime(task.getCreateTime())
                .owner(task.getOwner())
                .assignee(task.getAssignee())
                .processInstanceId(task.getProcessInstanceId())
                .processDefinitionId(task.getProcessDefinitionId())
                .executionId(task.getExecutionId())
                .claimTime(task.getClaimTime())
                .claimedBy(task.getClaimedBy())
                .suspendedTime(task.getSuspendedTime())
                .suspendedBy(task.getSuspendedBy())
                .taskLocalVariables(task.getTaskLocalVariables())
                .processVariables(task.getProcessVariables())
                .taskDefinitionKey(task.getTaskDefinitionKey())
                .formKey(task.getFormKey())
                ///.raw(task)
                .build();
    }
}